/******************************************************************
 *
 * prism.cpp - Prism's base class
 *
 * Zoltan Dekany (dekaaa@freemail.hu)
 *
 ******************************************************************/

#include <SDL/SDL.h>
#include <SDL/SDL_opengl.h>
#include <SDL/SDL_video.h>
#include <SDL/SDL_image.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <stdio.h>
#include <dirent.h>


// Temporary includes (probably)
#include <stdio.h>

#if defined(__APPLE__) && defined(__MACH__)
#include <OpenGL/gl.h>
#include <OpenGL/glu.h>
#endif

#ifdef WIN32
#include <windows.h>
#endif

#include "messages.h"
#include "prism.h"
#include "config.h"
#include "types.h"
#include "memory.h"


// Define part
#define SUCCESS     0
#define UNSUCCESS   1

#define LoadJPG( file )  IMG_LoadJPG_RW( SDL_RWFromFile( file, "rb" ) )


// Global definitions
char * SupportedImages[] = {
    "jpg",
    "JPG",
    "png",
    "PNG",
    NULL
};

enum ProcessingOrder {
    FILES = 0,
    DIRS
};

enum FileType {
    UNABLE_TO_OPEN,
    ITS_A_DIRECTORY,
    ITS_A_FILE,
    ITS_AN_IMAGE
};


// Global variables
int state = UNDEFINED_MODE;
bool showExif       = SHOW_EXIF;
bool showHistogram  = SHOW_HISTOGRAM;
bool showHidden     = false;

struct Image * CurrentImage = NULL;


// Function declarations
bool   isItAnImage( char * );
char * getFileExtension( char * );
char * getFilePath( char * );
int    parseParams( int, char ** );
int    getFileType( char *,  bool );
void   printHelp( void );
void   printVersion( void );
void   printAbout( void ); 
void   initScreen( void );
void   storeImage( void );
void   showImage( void );
void   addDir( char *, bool );
void   setCurrentImage( char * );
void   addFileToQueue( char * );


// Function definitions
char * getFileExtension( char * name )
{
    DEBUG1( "Entering..." );

    char * pos = NULL;
    char * ext = allocateVariable( getFileExtension_ext, 10, FILL );
    int length;

    pos = strrchr( name, '.' );
    if (pos) { 
        pos++;
        length = strlen( pos );          // There is an extension
        strncpy( ext, pos, length );
    } 

    DEBUG1( "Leaving..." );
    return ext;
}

bool isItAnImage( char * name )
{
    DEBUG1( "Entering..." );

    char * extension = NULL;
    bool retval = false;
    int i = 0; 

    DEBUG2( "The question is:", name );

    extension = getFileExtension( name );
    if (extension == NULL)  // If there is no extension
        return retval;

    DEBUG2( "Received extension is:", extension );

    while (SupportedImages[i]) {
        if ((strlen( extension )) != (strlen( SupportedImages[i] ))) {
            i++;
            continue;
        }

        if (strncmp( SupportedImages[i], extension, strlen( extension ) ) == 0) {
            retval = true;
            DEBUG1( "Extension is supported" );
            break;
        }

        i++;
    }

    DEBUG1( "Leaving..." );
    return retval;
}

int getFileType( char * name,  bool check ) // Rename this function
{
    DEBUG1( "Entering..." );

    struct stat file_stats;

    if ((stat( name, &file_stats )) == -1) {
        WARNING2( "Unable to open a file:", name ); 
        DEBUG1( "Leaving..." );
        return UNABLE_TO_OPEN;
    }

    if (S_ISDIR(file_stats.st_mode)) {
        DEBUG1( "Its a DIRECTORY" );
        DEBUG1( "Leaving..." );
        return ITS_A_DIRECTORY;
    }

    if ((check) && (isItAnImage( name ))) {
        DEBUG1( "Its an IMAGE" );
        DEBUG1( "Leaving..." );
        return ITS_AN_IMAGE;
    }

    DEBUG1( "Its a normal file" );
    DEBUG1( "Leaving..." );
    return ITS_A_FILE;
}

void printVersion( void ) 
{ 
    DEBUG1( "Entering..." ); 
    
    printf( "Prism version 0.1.0\n" ); 

    DEBUG1( "Leaving..." );
    return;
}

void printAbout( void ) 
{ 
    DEBUG1( "Entering..." ); 
    
    printf( "Prism, The Picture Viewer\nCreated by Zoltán Dékány (dekaaa@freemail.hu)\n2011\n" ); 

    DEBUG1( "Leaving..." );
    return;
}

void getResolution( char * resolution ) //TODO: This should move to another file
{
    DEBUG1( "Entering..." );

    WARNING1( "TODO: Implement this function" );
    
    DEBUG1( "Leaving..." );
    return;
}

void setCurrentImage( char * name )
{
    DEBUG1( "Entering..." );

    static bool currentSet = false;

    if (!currentSet) {
        INFO_STRING( "The current image is:", name );
        currentSet = true;
    }

    DEBUG1( "Leaving..." );
    return;
}

void addFileToQueue( char * name )
{
    DEBUG1( "Entering..." );
    
    INFO_STRING( "Added:", name );

    DEBUG1( "Leaving..." );
    return;
}

char * getFilePath( char * name )
{
    DEBUG1( "Entering..." );
    
    char * pos      = NULL;
    char * path     = allocateVariable( getFilePath_path, 256, FILL );
    char * thisPath = allocateVariable( getFilePath_thisPath, 256, FILL );
    int length;

    
    if (!getcwd( thisPath, 256 )) {
        ERROR1( "Unable to get the working directory" );
    }        
    DEBUG2( "The thisPath variable is:", thisPath );
    
    strcat( thisPath, "/" );

    pos = strrchr( name, '/' );
    if ( pos ) { 
        length = pos - name;          // Path found
        length += 2;

        strncpy( path, name, length );
        path[length-1] = '\0';

        DEBUG2( "The path variable is:", path );

        // This is a special case:
        // If prism is called like this: prism -a ./pic.JPG
        // the upper code will add the './' to the current path.
        // This check awoid this.
        if ((strncmp( path, "./", 2 )) != 0) {
            strcat( thisPath, path );
            DEBUG1( "Paths merged...." );
        }
    } 

    DEBUG2( "Returning path:", thisPath );
    DEBUG1( "Leaving..." );
    return thisPath;
}

void addDir( char * name, bool recursive )
{
    DEBUG1( "Entering..." );

    //DIR * dirP = NULL;
    struct dirent ** entryList = NULL;
    int scandirResult;
    enum FileType ft;
    char * FullPath          = allocateMemory( 256 );
    char * ReceivedParameter = allocateMemory( 256 );
    char * OldWorkingDir     = allocateMemory( 256 );

    memset( ReceivedParameter, 0, 256 );
    memset( FullPath, 0, 256 );
    memset( OldWorkingDir, 0, 256 );

    if (!getcwd( OldWorkingDir, 256 )) {
        ERROR1( "Unable to get the working directory" );
    }

    strcat( ReceivedParameter, name );
    DEBUG2( "Received parameter:", ReceivedParameter );

    if (((strncmp( ReceivedParameter, "./", 2 )) == 0) &&
            (strlen(ReceivedParameter) == 2)) {
//        name = allocateVariable( addDir_name, 256, FILL );

        if (!getcwd( ReceivedParameter, 256 )) {
            ERROR1( "Unable to get the working directory" );
        }
        strcat( ReceivedParameter, "/" );
    }

    if ((chdir( ReceivedParameter ) < 0)) {
        ERROR2( "Unable to change the working directory to", ReceivedParameter );
    }

    DEBUG2( "Working directory:", ReceivedParameter );
    /*dirP = opendir( ReceivedParameter );
    if (dirP == NULL) {
        ERROR1("Couldn't open the directory");
    }
*/
    scandirResult = scandir( ReceivedParameter, &entryList, 0, alphasort );
    if (scandirResult == 0) {
        ERROR2( "Couldn't open the directory", ReceivedParameter );
    }

    // Adding the files first in the given dir and later dig deeper into the directories
    for (int proc=FILES; proc <= DIRS; proc++) {

        for (int i=0; i<scandirResult; i++) {
            DEBUG2( "Next file is:", entryList[i]->d_name );

            // Skip if it is a '.' or '..' dir
            if (((strncmp( entryList[i]->d_name, ".", 1 ) == 0) && (strlen(entryList[i]->d_name) == 1)) ||
                    (strncmp( entryList[i]->d_name, "..", 2 ) == 0)) {
                DEBUG1( "This dir is not useful for me now... skipping! ;)" );

                continue;
            }

            // Handle hidden files
            if (strncmp( entryList[i]->d_name, ".", 1 ) == 0) {  
//                INFO_STRING( "This is a hidden file", entryList[i]->d_name );

                if (!showHidden) {
                    continue;
                }
            }

            ft = getFileType( entryList[i]->d_name, true );

            switch (ft) {

                case ITS_AN_IMAGE: {
                                       if (proc == DIRS) {
                                           continue;
                                       }

                                       strcat( FullPath, ReceivedParameter );
//                                       strcat( FullPath, "/" );
                                       strcat( FullPath, entryList[i]->d_name );

                                       DEBUG2( "Adding file:", FullPath );

                                       addFileToQueue( FullPath );

                                       memset( FullPath, 0, 256 );
                                       free( entryList[i] );
                                       break;
                                   }
                case ITS_A_DIRECTORY: {
                                          if (proc == FILES) {
                                              continue;
                                          }

                                          if (recursive) {
                                              strcat( FullPath, ReceivedParameter );
                                              strcat( FullPath, entryList[i]->d_name );
                                              strcat( FullPath, "/" );

                                              DEBUG2( "Recursive call... entering to", FullPath );

                                              addDir( FullPath, recursive );
                                              memset( FullPath, 0, 256 );
                                          }
                                          free( entryList[i] );
                                          break;
                                      }
                case ITS_A_FILE: {   // There is no need to care about this case.
                                     break;
                                 }
                default: {
                             WARNING1( "This is an imposible case..." );
                         }
            }
        }
    }

    if ((chdir( OldWorkingDir ) < 0)) {
        ERROR1( "Unable to change the working directory" );
    }

    freeMemory( (void *)FullPath );
    freeMemory( (void *)ReceivedParameter );
    freeMemory( (void *)OldWorkingDir );

    DEBUG1( "Leaving..." );
    return;
}

int parseParams( int argc, char * argv[] )
{
    DEBUG1( "Entering..." );

    bool recursive  = false;
    bool wholeDir   = false;
    char * path     = NULL;
    char * FullPath = allocateVariable( parseParams_fullPath, 256, FILL );
    char * name     = allocateVariable( parseParams_name, 256, FILL );
    enum FileType ft;
    int i;

    if (argc == 1) {
    //    SDL_Quit();
        exit( SUCCESS );
    }

    for (i=1; i<argc; i++)
        if ((strncmp( argv[i], "--recursive", 11 ) == 0) || (strncmp( argv[i], "-R", 2 ) == 0)) {
            DEBUG1( "Parameter received: RECURSIVE MODE" );
            recursive = true;
        }
        else if ((strncmp( argv[i], "--hidden", 5 ) == 0) || (strncmp( argv[i], "-H", 2 ) == 0)) {
            DEBUG1( "Parameter received: SHOW HIDDEN FILES" );
            showHidden = true;
        }
        else if ((strncmp( argv[i], "--all", 5 ) == 0) || (strncmp( argv[i], "-a", 2 ) == 0)) {
            DEBUG1( "Parameter received: ALL" );
            wholeDir = true;
        }
        else if ((strncmp( argv[i], "--quiet", 6 ) == 0) || (strncmp( argv[i], "-q", 2 ) == 0)) {
            DEBUG1( "Parameter received: QUIET MODE" );
            setLogLevel( LOGLEVEL_QUIET );
        }
        else if ((strncmp( argv[i], "--verbose", 9 ) == 0) || (strncmp( argv[i], "-v", 2 ) == 0)) {
            DEBUG1( "Parameter received: SHOW VERBOSE INFORMATION" );
            setLogLevel( LOGLEVEL_VERBOSE );
        }
        else if ((strncmp( argv[i], "--debug", 7 ) == 0) || (strncmp( argv[i], "-d", 2 ) == 0)) {
            DEBUG1( "Parameter received: SHOW DEBUG INFORMATION" );
            setLogLevel( LOGLEVEL_DEBUG );
        }
        else if ((strncmp( argv[i], "--about", 7 ) == 0) || (strncmp( argv[i], "-A", 2 ) == 0)) {
            DEBUG1( "Parameter received: SHOW ABOUT INFORMATION" );
            printAbout();
            exit( SUCCESS );
        }

    for (i=1; i<argc; i++)
    {
        if ((strncmp( argv[i], "-h", 2 ) == 0) || (strncmp( argv[i], "--help", 4 ) == 0)) {
            printHelp();
            exit( SUCCESS );
        }
        else if ((strncmp( argv[i], "---picturen-only-mode", 17 ) == 0) || (strncmp( argv[i], "-p", 2 ) == 0)) {
            DEBUG1( "Parameter received: MODE: PICTURE ONLY" );

            if (state != UNDEFINED_MODE) {
                WARNING2( "More than one mode switch was given", "Falling to \'Picture Only Mode\'" );
            }
            
            state = PICTURE_ONLY;
        }
        else if ((strncmp( argv[i], "--thumbsonly-mode", 17 ) == 0) || (strncmp( argv[i], "-t", 2 ) == 0)) {
            DEBUG1( "Parameter received: MODE: THUMBS ONLY" );

            if (state != UNDEFINED_MODE) {
                WARNING2( "More than one mode switch was given", "Falling to \'Thumbs Only Mode\'" );
            }

            state = THUMBS_ONLY;
        }
        else if ((strncmp( argv[i], "--mixed-mode", 12 ) == 0) || (strncmp( argv[i], "-m", 2 ) == 0)) {
            DEBUG1( "Parameter received: MODE: MIXED" );

            if (state != UNDEFINED_MODE) {
                WARNING2( "More than one mode switch was given", "Falling to \'Mixed Mode\'" );
            }

            state = PICTURE_AND_THUMBS;
        }
        else if ((strncmp( argv[i], "--version", 9 ) == 0) || (strncmp( argv[i], "-V", 2 ) == 0)) {
            DEBUG1( "Parameter received: SHOW VERSION INFORMATION" );
            printVersion();
        }
        else if ((strncmp( argv[i], "--exif", 6 ) == 0) || (strncmp( argv[i], "-E", 2 ) == 0)) {
            DEBUG1( "Parameter received: SHOW EXIF INFO" );
            showExif = true;
        }
        else if ((strncmp( argv[i], "--histogram", 11 ) == 0) || (strncmp( argv[i], "-H", 2 ) == 0)) {
            DEBUG1( "Parameter received: SHOW HISTOGRAM" );
            showHistogram = true;
        }
        else if ((strncmp( argv[i], "--resolution", 12 ) == 0) || (strncmp( argv[i], "-r", 2 ) == 0)) {
            DEBUG1( "Parameter received: RESOLUTION" );
            i++; 
            getResolution( argv[i] );
        }
        else if ((strncmp( argv[i], "--slideshow", 11 ) == 0) || (strncmp( argv[i], "-s", 2 ) == 0)) {
            DEBUG1( "Parameter received: START SLIDESHOW" );
            state |= SLIDESHOW;
        }
        // These are handled in the previous turn
        else if ((strncmp( argv[i], "--recursive", 11 ) == 0) || 
                (strncmp( argv[i], "-R", 2 ) == 0) ||
                (strncmp( argv[i], "--quiet", 6 ) == 0) || 
                (strncmp( argv[i], "-q", 2 ) == 0) ||
                (strncmp( argv[i], "--verbose", 9 ) == 0) || 
                (strncmp( argv[i], "-v", 2 ) == 0) ||
                (strncmp( argv[i], "--debug", 7 ) == 0) || 
                (strncmp( argv[i], "-d", 2 ) == 0) ||
                (strncmp( argv[i], "--about", 7 ) == 0) || 
                (strncmp( argv[i], "-A", 2 ) == 0) ||
                (strncmp( argv[i], "--all", 5 ) == 0) || 
                (strncmp( argv[i], "-a", 2 ) == 0)) {
        }
        else {
            DEBUG1( "Trying to parse the remaining parameters as an image file" );

            ft = getFileType( argv[i], true );
            switch (ft) {
                case UNABLE_TO_OPEN: {
                                         ERROR2( "Unable to open file", argv[i] );
                                         break;
                                     }
                case ITS_A_DIRECTORY: {
                                          DEBUG1( "It's a directory... calling addDir()" );
                                          addDir( argv[i], recursive );
                                          break;
                                      }
                case ITS_AN_IMAGE: {
                                     DEBUG1( "It is an image..." );

                                     path = getFilePath( argv[i] );
                                     strcat( FullPath, path );

                                     if ((strncmp( argv[i], "./", 2 )) == 0) {
                                         name = strrchr( argv[i], '/' );
                                         name++;
                                         strcat( FullPath, name );
                                     } else {
                                         strcat( FullPath, argv[i] );
                                     }


                                     DEBUG2( "Path:", path );
                                     DEBUG2( "FullPath:", FullPath );

                                     addFileToQueue( FullPath );
                                     setCurrentImage( FullPath );
                                     
                                     if (recursive) {
                                         addDir( path, true );
                                     } else if (wholeDir) {
                                         addDir( path, false );
                                     }

                                     DEBUG2( "FullPath:", FullPath );

                                     memset( FullPath, 0, 256 );
                                     break;
                                 }
                case ITS_A_FILE: {   // We don't need to do anything in this case
                                     DEBUG1( "It is a file..." );
                                     break;
                                 }
                default: {  // Something is wrong
                             WARNING1( "This is an imposible case" );
                         }
            }
        }
    }

    DEBUG1( "Leaving..." );
    return SUCCESS;
}

void printHelp()
{
    DEBUG1( "Entering..." );

    int i = 0;

    char * message[] = {
        "Prism 0.1 - The picture viewer",
        "Usage: prism [options] [files]",
        "",
        "--help, -h                 Show Help",
        "--quiet, -q                Loglevel to quiet",
        "--verbose, -v              Loglevel to verbose",
        "--debug, -d                Loglevel to debug",
        "--picture-only-mode, -p    Start Prism in picture only mode",
        "--thumbsonly-mode, -t      Start Prism in thumbsonly mode",
        "--mixed-mode, -m           Start Prism in mixed mode",
        "--exif, -E                 Show exif info",
        "--histogram, -H            Show histogram",
        "--all, -a                  Show the whole directory",
        "--recursive, -R            Find all image files in the directory structure",
        "--slideshow, -s            Start slideshow",
        "--resolution, -r           Start program in the given resolution",
        "--about, -A                About this program",
        "--version, -V              Show version information",
        NULL
    };

    while (message[i])
    {
        printf( "%s\n", message[i] );
        i++;
    }

    printf( "\n" );

    DEBUG1( "Leaving..." );
    return;
}


void initScreen( void )
{
    DEBUG1( "Entering..." );

#ifdef USE_OPENGL

    if (SDL_Init( SDL_INIT_VIDEO ))
        ERROR2( "Unable to initialize SDL:", SDL_GetError() );

    SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );
    SDL_VideoInfo * info = (SDL_VideoInfo *)SDL_GetVideoInfo();
    SDL_Surface * screen = SDL_SetVideoMode( info->current_w, info->current_h, 16, SDL_OPENGL | SDL_FULLSCREEN );
    glEnable( GL_TEXTURE_2D );
    glClearColor( 0.0f, 0.0f, 0.0f, 0.0f );
    glViewport( 0, 0, 640, 480 );
    glClear( GL_COLOR_BUFFER_BIT );
    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
    glOrtho( 0.0f, 640, 480, 0.0f, -1.0f, 1.0f );
    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity();

#else

#endif
    DEBUG1( "Leaving..." );
    return;
}

void storeImage( void )
{
    DEBUG1( "Entering..." );

    WARNING1( "Implement this function" );

    DEBUG1( "Leaving..." );
    return;
}

void showImage( void )
{
    DEBUG1( "Entering..." );
    
    GLuint colorNum;
    GLenum textureFormat;
    GLuint texture;
    SDL_Surface * img = NULL;

    if ((img = LoadJPG("TS1.JPG")) == NULL)
        ERROR2( "SDL could not load image:", SDL_GetError() );

    colorNum = img->format->BytesPerPixel;
    switch (colorNum)
    {
        case 4: 
            {
                if (img->format->Rmask == 0x000000FF)
                    textureFormat = GL_RGBA;
                else
                    textureFormat = GL_BGRA;
                break;
            }
        case 3:
            {
                if (img->format->Rmask == 0x000000FF)
                    textureFormat = GL_RGB;
                else
                    textureFormat = GL_BGR;
                break;
            }
        default: WARNING1( "The image is not true color" );
    }

    glGenTextures( 1, &texture );
    glBindTexture( GL_TEXTURE_2D, texture );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );

    glTexImage2D( GL_TEXTURE_2D, 0, colorNum, img->w, img->h, 0, textureFormat, GL_UNSIGNED_BYTE, img->pixels ); 

    SDL_FreeSurface( img );

    DEBUG1( "Leaving..." );
    return;
}

int main( int argc, char* argv[] )
{
    DEBUG1( "Entering..." );

//    addDir( "valami.jpg" );

    initMemoryManager();

    parseParams( argc, argv );

    initScreen();

    showImage();

//    SDL_Delay( 2000 );
    SDL_Quit();
    
    DEBUG1( "Leaving..." );
    return SUCCESS;
}


